// =============================================
// FD Perlin Noise
// Made by Ubik and Claude 2026
// =============================================
// Generates animated greyscale Perlin noise.
// Uses 3D noise with time as the Z axis so the
// pattern morphs in place rather than sliding.
// No video input required - this is a generator.
// =============================================

// ISADORA_PLUGIN_DESC("Generates animated B&W Perlin noise with multiple modes, contrast, brightness, and threshold controls.")

// -- Noise Parameters --
// ISADORA_FLOAT_PARAM(noise_size, size, 1.0, 100.0, 10.0, "Scale of the noise. Lower = larger blobs, higher = finer detail.")
// ISADORA_FLOAT_PARAM(noise_speed, sped, 0.0, 10.0, 1.0, "Animation speed. 0 = frozen.")

// -- Mode Selector (integer) --
// 0=Classic, 1=Turbulence, 2=Ridged, 3=Billowy, 4=Warped Domain
// ISADORA_INT_PARAM(noise_mode, mode, 0, 4, 0, "Noise mode: 0=Classic 1=Turbulence 2=Ridged 3=Billowy 4=Warped")

// -- Octaves Control --
// ISADORA_INT_PARAM(octaves, octv, 1, 8, 5, "Number of noise layers (octaves). More = finer detail, higher cost.")

// -- Contrast & Tone Controls --
// ISADORA_FLOAT_PARAM(contrast, cont, 0.0, 5.0, 1.0, "Contrast amount. 1.0 = normal. Higher pushes greys toward B&W.")
// ISADORA_FLOAT_PARAM(brightness, brit, -1.0, 1.0, 0.0, "Brightness offset. 0 = normal. Positive = brighter, negative = darker.")
// ISADORA_FLOAT_PARAM(threshold, thrs, 0.0, 1.0, 0.0, "Hard threshold. 0 = off (smooth greyscale). 1 = full binary B&W cutoff at 0.5.")
// ISADORA_FLOAT_PARAM(gamma, gama, 0.1, 5.0, 1.0, "Gamma curve. 1.0 = linear. Less than 1 = brighter mids. Greater than 1 = darker mids.")

uniform float noise_size;
uniform float noise_speed;
uniform int noise_mode;
uniform int octaves;
uniform float contrast;
uniform float brightness;
uniform float threshold;
uniform float gamma;
uniform float iTime;
uniform vec3 iResolution;


// ============================================
// 3D PERLIN NOISE
// ============================================
// Time is used as the 3rd dimension so the
// noise evolves/morphs in place instead of
// scrolling across the screen.
// ============================================

// 3D hash: deterministic pseudo-random gradient vectors
vec3 hash3(vec3 p) {
    p = vec3(dot(p, vec3(127.1, 311.7, 74.7)),
             dot(p, vec3(269.5, 183.3, 246.1)),
             dot(p, vec3(113.5, 271.9, 124.6)));
    return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}

// Classic 3D Perlin noise
float perlinNoise3D(vec3 p) {
    vec3 i = floor(p);
    vec3 f = fract(p);

    // Quintic Hermite interpolation
    vec3 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);

    // Eight corners of the cube
    float n000 = dot(hash3(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0));
    float n100 = dot(hash3(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0));
    float n010 = dot(hash3(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0));
    float n110 = dot(hash3(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0));
    float n001 = dot(hash3(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0));
    float n101 = dot(hash3(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0));
    float n011 = dot(hash3(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0));
    float n111 = dot(hash3(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0));

    // Trilinear interpolation
    float nx00 = mix(n000, n100, u.x);
    float nx10 = mix(n010, n110, u.x);
    float nx01 = mix(n001, n101, u.x);
    float nx11 = mix(n011, n111, u.x);

    float nxy0 = mix(nx00, nx10, u.y);
    float nxy1 = mix(nx01, nx11, u.y);

    return mix(nxy0, nxy1, u.z);
}


// ============================================
// NOISE MODES (all return 0..1 range)
// All use 3D noise with time on the Z axis.
// ============================================

// Mode 0: Classic FBM
float fbmClassic(vec3 p, int oct) {
    float value = 0.0;
    float amplitude = 0.5;
    float frequency = 1.0;
    for (int i = 0; i < 8; i++) {
        if (i >= oct) break;
        value += amplitude * perlinNoise3D(p * frequency);
        frequency *= 2.0;
        amplitude *= 0.5;
    }
    return value * 0.5 + 0.5;
}

// Mode 1: Turbulence - abs of noise, cloud/smoke look
float fbmTurbulence(vec3 p, int oct) {
    float value = 0.0;
    float amplitude = 0.5;
    float frequency = 1.0;
    for (int i = 0; i < 8; i++) {
        if (i >= oct) break;
        value += amplitude * abs(perlinNoise3D(p * frequency));
        frequency *= 2.0;
        amplitude *= 0.5;
    }
    return value;
}

// Mode 2: Ridged - sharp ridges/veins
float fbmRidged(vec3 p, int oct) {
    float value = 0.0;
    float amplitude = 0.5;
    float frequency = 1.0;
    float prev = 1.0;
    for (int i = 0; i < 8; i++) {
        if (i >= oct) break;
        float n = 1.0 - abs(perlinNoise3D(p * frequency));
        n = n * n;
        value += n * amplitude * prev;
        prev = n;
        frequency *= 2.0;
        amplitude *= 0.5;
    }
    return value;
}

// Mode 3: Billowy - soft puffy clouds
float fbmBillowy(vec3 p, int oct) {
    float value = 0.0;
    float amplitude = 0.5;
    float frequency = 1.0;
    for (int i = 0; i < 8; i++) {
        if (i >= oct) break;
        float n = perlinNoise3D(p * frequency);
        n = n * 0.5 + 0.5;
        n = n * n;
        value += amplitude * n;
        frequency *= 2.0;
        amplitude *= 0.5;
    }
    return value;
}

// Mode 4: Warped Domain - marble-like organic swirls
float fbmWarped(vec3 p, int oct) {
    vec2 q = vec2(
        fbmClassic(p + vec3(0.0, 0.0, 0.0), oct),
        fbmClassic(p + vec3(5.2, 1.3, 2.8), oct)
    );

    vec2 r = vec2(
        fbmClassic(p + vec3(4.0 * q.x + 1.7, 4.0 * q.y + 9.2, 0.0), oct),
        fbmClassic(p + vec3(4.0 * q.x + 8.3, 4.0 * q.y + 2.8, 0.0), oct)
    );

    return fbmClassic(p + vec3(4.0 * r, 0.0), oct);
}


// ============================================
// CONTRAST & TONE SHAPING
// ============================================

float applyTone(float n) {
    // 1. Gamma
    n = pow(clamp(n, 0.0, 1.0), gamma);

    // 2. Contrast: expand around 0.5
    n = (n - 0.5) * contrast + 0.5;

    // 3. Brightness offset
    n = n + brightness;

    // 4. Clamp
    n = clamp(n, 0.0, 1.0);

    // 5. Threshold: blend smooth greyscale toward hard B&W
    if (threshold > 0.001) {
        float binary = step(0.5, n);
        n = mix(n, binary, threshold);
    }

    return n;
}


// ============================================
// MAIN
// ============================================

void main() {
    vec2 uv = gl_FragCoord.xy / iResolution.xy;

    // Build 3D coordinate: XY from screen, Z from time
    // Time on Z axis = noise morphs in place
    vec3 p = vec3(uv * noise_size, iTime * noise_speed);

    // Select noise mode
    float n = 0.0;

    if (noise_mode == 0) {
        n = fbmClassic(p, octaves);
    } else if (noise_mode == 1) {
        n = fbmTurbulence(p, octaves);
    } else if (noise_mode == 2) {
        n = fbmRidged(p, octaves);
    } else if (noise_mode == 3) {
        n = fbmBillowy(p, octaves);
    } else {
        n = fbmWarped(p, octaves);
    }

    // Apply tone shaping
    n = applyTone(n);

    // Output greyscale
    gl_FragColor = vec4(vec3(n), 1.0);
}
